package com.moc.web.controller.common;

import cn.hutool.core.util.ObjectUtil;
import com.moc.common.core.domain.AjaxResult;
import com.moc.common.core.domain.entity.SysMinioFile;
import com.moc.common.utils.file.FileUtils;
import com.moc.common.utils.minio.MinioUploadUtil;
import com.moc.system.service.ISysMinioFileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.Map;

/**
 * MinIO文件上传控制器
 * 整合MinioUploadUtil工具类，提供文件上传、下载、删除、预签名URL生成等功能
 */
@Slf4j
@RestController
@RequestMapping("/minio")
@RequiredArgsConstructor
@Tag(name = "MinIO文件上传接口")
public class MinioUploadController {

    private final MinioUploadUtil minioUploadUtil;
    private final ISysMinioFileService minioFileService;


    /**
     * 文件上传接口
     * 支持单文件上传，自动处理文件名冲突（添加UUID前缀）
     */
    @Operation(summary = "文件上传接口", description = "支持单文件上传")
    @PostMapping("/upload")
    public AjaxResult uploadFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            return AjaxResult.error("未接收到文件");
        }

        try {
            String md5 = minioUploadUtil.calculateMD5(file);
            SysMinioFile minioFile = minioFileService.selectSysMinioFileByMd5(md5);

            if (ObjectUtil.isNotNull(minioFile)) {
                String presignedUrl = minioUploadUtil.buildAccessPath(minioFile.getStoragePath());
                return AjaxResult.success(presignedUrl);
            }

            // 调用工具类上传文件，自动处理文件名和流
            minioFile = minioUploadUtil.upload(file);
            minioFileService.insertSysMinioFile(minioFile);

            log.info("文件上传成功：文件访问URL={}", minioFile.getStoragePath());
            return AjaxResult.success(minioUploadUtil.getPresignedUrl(minioFile.getStoragePath())); // 返回文件访问URL

        } catch (Exception e) {
            log.error("文件上传失败：{}", e.getMessage(), e);
            return AjaxResult.error("文件上传失败：" + e.getMessage());
        }
    }

    /**
     * 文件下载接口
     * 通过文件名获取文件流（支持断点续传）
     */
    @Operation(summary = "文件下载接口", description = "支持断点续传")
    @PostMapping("/download/{fileName}")
    public void downloadFile(@PathVariable String fileName, HttpServletResponse response) {
        try {
            // 查询文件信息
            SysMinioFile minioFile = minioFileService.selectSysMinioFileByName(fileName);
            if (ObjectUtil.isNull(minioFile)) {
                throw new RuntimeException("文件不存在");
            }

            // 调用工具类获取文件流
            InputStream inputStream = minioUploadUtil.download(minioFile.getStoragePath());

            // 设置响应头
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            FileUtils.setAttachmentResponseHeader(response, minioFile.getOriginalFilename());
            FileUtils.writeBytes(inputStream, response.getOutputStream());

        } catch (Exception e) {
            log.error("文件下载失败：{}", e.getMessage(), e);
        }
    }

    /**
     * 文件删除接口
     * 根据文件名删除MinIO中的文件
     */
    @Operation(summary = "文件删除接口", description = "根据文件名删除MinIO中的文件")
    @DeleteMapping("/delete/{fileName}")
    public AjaxResult deleteFile(@PathVariable String fileName) {
        try {
            // 查询文件信息
            SysMinioFile minioFile = minioFileService.selectSysMinioFileByName(fileName);
            if (ObjectUtil.isNull(minioFile)) {
                return AjaxResult.error("文件不存在，删除失败");
            }

            // 调用工具类删除文件
            boolean isDeleted = minioUploadUtil.delete(minioFile.getStoragePath());

            if (isDeleted) {
                minioFileService.removeMinioFileByIds(minioFile.getId());
                log.info("文件删除成功");
                return AjaxResult.success("文件删除成功");
            } else {
                return AjaxResult.error("文件不存在，删除失败");
            }

        } catch (Exception e) {
            log.error("文件删除失败：{}", e.getMessage(), e);
            return AjaxResult.error("文件删除失败：" + e.getMessage());
        }
    }

    /**
     * 生成预签名URL接口
     * 生成带有效期的文件访问URL（用于前端直接访问私有Bucket中的文件）
     */
    @Operation(summary = "生成预签名URL接口", description = "生成带有效期的文件访问URL")
    @GetMapping("/generatePresignedUrl/{fileName}")
    public AjaxResult generatePresignedUrl(@PathVariable String fileName) {
        try {
            // 查询文件信息
            SysMinioFile minioFile = minioFileService.selectSysMinioFileByName(fileName);
            if (ObjectUtil.isNull(minioFile)) {
                return AjaxResult.error("文件不存在");
            }

            // 调用工具类生成预签名URL（有效期1小时）
            String presignedUrl = minioUploadUtil.getPresignedUrl(minioFile.getStoragePath());

            log.info("生成预签名URL成功： URL={}", presignedUrl);
            return AjaxResult.success(presignedUrl);

        } catch (Exception e) {
            log.error("生成预签名URL失败：{}", e.getMessage(), e);
            return AjaxResult.error("生成预签名URL失败：" + e.getMessage());
        }
    }

    // ------------------------------ 分片上传接口 ------------------------------

    /**
     * 初始化分片上传
     * @param originalFilename 原始文件名
     * @param contentType 文件MIME类型
     * @param fileTotalSize 文件总大小
     * @param customPartSize 自定义分片大小（可选）
     * @return 初始化结果（uploadId、分片大小、预期分片数等）
     */
    @Operation(summary = "初始化分片上传", description = "初始化分片上传")
    @PostMapping("/multipart/init")
    public AjaxResult initMultipartUpload(
            @RequestParam String originalFilename,
            @RequestParam String contentType,
            @RequestParam long fileTotalSize,
            @RequestParam(required = false) Long customPartSize) {
        try {
            Map<String, Object> result = minioUploadUtil.initMultipartUpload(
                    originalFilename, contentType, customPartSize, fileTotalSize);
            return AjaxResult.success(result);
        } catch (Exception e) {
            log.error("分片上传初始化失败：{}", e.getMessage(), e);
            return AjaxResult.error("分片上传初始化失败：" + e.getMessage());
        }
    }

    /**
     * 服务端接收分片并上传（非前端直传场景）
     * @param uploadId 分片任务ID
     * @param partNumber 分片序号
     * @param partFile 分片文件
     * @param partMd5 分片MD5（用于校验）
     * @return 分片上传结果
     */
    @Operation(summary = "接收分片并上传", description = "接收分片并上传")
    @PostMapping("/multipart/upload")
    public AjaxResult uploadPartByServer(
            @RequestParam String uploadId,
            @RequestParam int partNumber,
            @RequestParam MultipartFile partFile,
            @RequestParam String partMd5) {
        if (partFile == null || partFile.isEmpty()) {
            return AjaxResult.error("未接收到分片文件");
        }
        try {
            Map<String, Object> result = minioUploadUtil.uploadPartByServer(
                    uploadId, partNumber, partFile, partMd5);
            return AjaxResult.success(result);
        } catch (Exception e) {
            log.error("服务端上传分片失败：uploadId={}, partNumber={}", uploadId, partNumber, e);
            return AjaxResult.error("分片" + partNumber + "上传失败：" + e.getMessage());
        }
    }

    /**
     * 查询已上传分片（断点续传用）
     * @param uploadId 分片任务ID
     * @return 已上传分片列表（序号→大小）
     */
    @Operation(summary = "查询已上传分片", description = "查询已上传分片")
    @GetMapping("/multipart/list")
    public AjaxResult listUploadedParts(@RequestParam String uploadId) {
        try {
            Map<Integer, Long> uploadedParts = minioUploadUtil.listUploadedParts(uploadId);
            return AjaxResult.success(uploadedParts);
        } catch (Exception e) {
            log.error("查询已上传分片失败：uploadId={}", uploadId, e);
            return AjaxResult.error("查询已上传分片失败：" + e.getMessage());
        }
    }

    /**
     * 完成分片上传（合并分片）
     * @param uploadId 分片任务ID
     * @param partCount 总分片数
     * @param originalFilename 原始文件名
     * @param fileTotalSize 文件总大小
     * @param fileTotalMd5 文件总MD5
     * @param customDir 自定义存储目录（可选）
     * @return 合并后的文件信息
     */
    @Operation(summary = "完成分片上传", description = "完成分片上传")
    @PostMapping("/multipart/complete")
    public AjaxResult completeMultipartUpload(
            @RequestParam String uploadId,
            @RequestParam int partCount,
            @RequestParam String originalFilename,
            @RequestParam long fileTotalSize,
            @RequestParam String fileTotalMd5,
            @RequestParam(required = false) String customDir) {
        try {
            // 检查是否已存在相同MD5的文件（避免重复上传）
            SysMinioFile existFile = minioFileService.selectSysMinioFileByMd5(fileTotalMd5);
            if (ObjectUtil.isNotNull(existFile)) {
                // 清理当前分片临时文件
                minioUploadUtil.abortMultipartUpload(uploadId, partCount);
                return AjaxResult.success(minioUploadUtil.getPresignedUrl(existFile.getStoragePath()));
            }

            // 执行分片合并
            SysMinioFile minioFile = minioUploadUtil.completeMultipartUpload(
                    uploadId, partCount, originalFilename, fileTotalSize, fileTotalMd5, customDir);
            // 保存文件信息到数据库
            minioFileService.insertSysMinioFile(minioFile);

            log.info("分片上传完成：文件ID={}, 路径={}", minioFile.getId(), minioFile.getStoragePath());
            return AjaxResult.success(minioUploadUtil.getPresignedUrl(minioFile.getStoragePath()));
        } catch (Exception e) {
            log.error("分片合并失败：uploadId={}", uploadId, e);
            return AjaxResult.error("分片合并失败：" + e.getMessage());
        }
    }

    /**
     * 取消分片上传（清理临时文件）
     * @param uploadId 分片任务ID
     * @param partCount 已上传分片数（可选，为null则自动查询）
     * @return 操作结果
     */
    @Operation(summary = "取消分片上传", description = "取消分片上传")
    @PostMapping("/multipart/abort")
    public AjaxResult abortMultipartUpload(
            @RequestParam String uploadId,
            @RequestParam(required = false) Integer partCount) {
        try {
            minioUploadUtil.abortMultipartUpload(uploadId, partCount);
            return AjaxResult.success("分片上传已取消，临时文件已清理");
        } catch (Exception e) {
            log.error("取消分片上传失败：uploadId={}", uploadId, e);
            return AjaxResult.error("取消分片上传失败：" + e.getMessage());
        }
    }
}
